import type { Point, Feature } from 'geojson'
import type { SymbolStyle } from './types'
import BaseLayer from './base-layer'
import { compare } from '@/widgets'

export interface BaseSymbolLayerOption<T> {
  id: string
  name: string
  getFeature: (t: T) => Feature<Point>
  style: SymbolStyle
  textField?: string | mapboxgl.Expression
  iconRotate?: number | mapboxgl.Expression
  minZoom?: number
  textVisible?: boolean
  visible?: boolean
}

export default class BaseSymbolLayer<T> extends BaseLayer {
  public style: SymbolStyle
  private isTextVisible: boolean = false
  private iconRotate: number | mapboxgl.Expression = 0
  private textField: string | mapboxgl.Expression = ''
  private getFeature: (t: T) => Feature<Point>
  private minZoom: number | undefined = undefined

  constructor(opts: BaseSymbolLayerOption<T>, dat: T[] | undefined = undefined) {
    super(opts.id, opts.name, opts.visible !== false, dat?.map((e) => opts.getFeature(e)))
    this.style = this.makeStyle(opts.style)
    this.iconRotate = opts.iconRotate || 0
    this.textField = opts.textField || ''
    this.getFeature = opts.getFeature
    this.minZoom = opts.minZoom
    this.isTextVisible = opts.textVisible !== false
  }

  public set textVisible(ok: boolean) {
    if (this.isTextVisible === ok) {
      return
    }
    this.isTextVisible = ok
    if (this.map) {
      const f = this.makeTextField(ok)
      this.map.setLayoutProperty(this.id, 'text-field', f)
    }
  }

  public get textVisible(): boolean {
    return this.isTextVisible
  }

  public setMinZoom(z: number) {
    this.minZoom = z
    if (this.map) {
      this.map.setLayerZoomRange(this.id, z, 100.0)
    }
  }

  public setIconRotate(r: number | mapboxgl.Expression) {
    this.iconRotate = r
    if (this.map) {
      this.map.setLayoutProperty(this.id, 'icon-rotate', this.iconRotate)
    }
  }

  public setStyle(style: SymbolStyle) {
    style = this.makeStyle(style)
    const origin = this.style
    this.style = style
    const { map, id } = this
    if (!map) {
      return
    }
    if (origin.fontScale !== style.fontScale) {
      map.setLayoutProperty(id, 'text-field', this.makeTextField(this.textVisible))
    }
    if (origin.icon !== style.icon) {
      map.setLayoutProperty(id, 'icon-image', style.icon)
    }
    if (origin.textMaxWidth !== style.textMaxWidth) {
      map.setLayoutProperty(id, 'text-max-width', style.textMaxWidth)
    }
    if (origin.textJustify !== style.textJustify) {
      map.setLayoutProperty(id, 'text-justify', style.textJustify)
    }
    if (!compare(origin.textOffset, style.textOffset)) {
      map.setLayoutProperty(id, 'text-offset', style.textOffset)
    }
    if (origin.color !== style.color) {
      map.setPaintProperty(id, 'text-color', style.color)
      map.setPaintProperty(id, 'icon-color', style.color)
    }
    if (origin.textOpacity !== style.textOpacity) {
      map.setPaintProperty(id, 'text-opacity', style.textOpacity)
    }
  }

  public clearData() {
    this.repaint([])
  }

  public updateData(lst: T[] | undefined) {
    if (!lst || lst.length === 0) {
      this.repaint([])
      return
    }
    const fLst: Feature<Point>[] = lst.map((e) => this.getFeature(e))
    this.repaint(fLst)
  }

  private makeStyle(s: SymbolStyle): SymbolStyle {
    return Object.assign(
      {
        textMaxWidth: 10,
        textJustify: 'center'
      },
      s
    )
  }

  protected makeMapBoxGLLayer(): Omit<mapboxgl.Layer, 'source'> {
    const ret: Omit<mapboxgl.SymbolLayer, 'source'> = {
      id: this.id,
      type: 'symbol',
      layout: {
        'text-field': this.makeTextField(this.isTextVisible),
        'icon-rotate': this.iconRotate,
        'text-max-width': this.style.textMaxWidth || 10,
        'text-justify': this.style.textJustify || 'center',
        'icon-allow-overlap': true,
        'text-allow-overlap': true,
        'text-ignore-placement': true,
        'text-offset': this.style.textOffset
      },
      paint: {
        'text-color': this.style.color,
        'icon-color': this.style.color,
        'text-opacity': this.style.textOpacity
      }
    }
    if (this.style.icon) {
      ret.layout!['icon-image'] = this.style.icon
    }
    if (this.minZoom != null) {
      ret.minzoom = this.minZoom
    }
    return ret
  }

  private makeTextField(txtVisible: boolean): string | mapboxgl.Expression {
    if (txtVisible) {
      return ['format', this.textField, { 'font-scale': this.style.fontScale }]
    }
    return ''
  }
}
